using System;
using System.Collections;
using System.Threading;

using Pegasus.Diagnostics;
using Pegasus.Log4Net;

namespace Pegasus.Threading
{
    /// <summary>
    /// Summary description for AppScheduler2.
    /// </summary>
    public class AppScheduler2
    {
        // Local Static Values
		private ILog m_log = LogManager.GetLogger( typeof( AppScheduler2 ) );
		private static AppScheduler2 s_appScheduler = null;

        // Local Values
        private object m_lock = null;
        private SortedList m_pendingTasks = null;
        private Timer m_timer = null;

        /// <summary>
        /// Gets the app scheduler.
        /// This can be used when an instance is not wanted to be used.
        /// </summary>
        /// <value>The app scheduler.</value>
        public static AppScheduler2 AppScheduler
        {
            get
            {
                if( s_appScheduler == null )
                {
                    s_appScheduler = new AppScheduler2();
                }
                return s_appScheduler;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AppScheduler2"/> class.
        /// </summary>
        public AppScheduler2()
        {
            m_lock = new object();
            m_pendingTasks = new SortedList();
            m_timer = new Timer( new TimerCallback( ProcessTasks ), null, -1, -1 );
        }

        /// <summary>
        /// Schedules the task.
        /// </summary>
        /// <param name="task">The task.</param>
        public virtual void ScheduleTask( IScheduleTask task )
        {
            lock( m_lock )
            {
                // A bit strange but it work.  The sorted list is not allowed to
                // have to identical keys in it.  So if there is already a DateTime
                // object with the exact time then kick this object forward in time
                // by one millisecond until it's unique.
                DateTime occursAt = task.GetNextReoccurrence();
                while( m_pendingTasks.ContainsKey( occursAt ) )
                {
                    occursAt = occursAt.AddMilliseconds( 1 );
                }

                m_pendingTasks.Add( occursAt, task );
                ProcessTasks( null );
            }
        }

        /// <summary>
        /// Unschedules the task.
        /// </summary>
        /// <param name="task">The task.</param>
        public void UnscheduleTask( IScheduleTask task )
        {
            lock( m_lock )
            {
                int index = m_pendingTasks.IndexOfValue( task );
                if( index > -1 )
                {
                    m_pendingTasks.RemoveAt( index );
                    ProcessTasks( null );
                }
            }
        }

        /// <summary>
        /// Determines whether [is scheduled task] [the specified task].
        /// </summary>
        /// <param name="task">The task.</param>
        /// <returns>
        /// 	<c>true</c> if [is scheduled task] [the specified task]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsScheduledTask( IScheduleTask task )
        {
            return m_pendingTasks.ContainsValue( task );
        }

        /// <summary>
        /// Processes the tasks.
        /// </summary>
        /// <param name="state">The state.</param>
        private void ProcessTasks( object state )
        {
            lock( m_lock )
            {
                while( m_pendingTasks.Count > 0 )
                {
                    DateTime key = (DateTime) m_pendingTasks.GetKey( 0 );
                    if( key < DateTime.Now )
                    {
                        IScheduleTask task = (IScheduleTask) m_pendingTasks.GetByIndex( 0 );
                        m_pendingTasks.RemoveAt( 0 );

                        AppThreadPool.SystemPool.QueueUserWorkItem( new WaitCallback( TaskThreadProc ), task );
                    }
                    else
                    {
                        // drop out of loop
                        break;
                    }
                }

                if( m_pendingTasks.Count > 0 )
                {
                    // Set the time to fire for the next task
                    DateTime key = (DateTime) m_pendingTasks.GetKey( 0 );
                    TimeSpan next = key - DateTime.Now;

                    // Set the timer to fire just after the task expires
                    m_timer.Change( ( (int) next.TotalMilliseconds ) + 10, -1 );
                }
                else
                {
                    // Clear the timer we have no more task to run
                    m_timer.Change( -1, -1 );
                }
            }
        }

        /// <summary>
        /// Tasks the thread proc.
        /// </summary>
        /// <param name="state">The state.</param>
        protected virtual void TaskThreadProc( object state )
        {
            IScheduleTask task = (IScheduleTask) state;

            try
            {
                task.RunTask();
            }
            catch( Exception e )
            {
				m_log.Error( string.Format( "Unhandled Exception in task {0}", task.Name ), e );
			}
            finally
            {
                if( task.ReoccurringTask && !AppScheduler.IsScheduledTask( task ) )
                {
                    AppScheduler.ScheduleTask( task );
                }
            }
        }
    }
}
